﻿using System;
using System.Diagnostics;
using System.IO;
using System.Threading;
using NAudio.Wave;
using NAudio.Wave.SampleProviders;
using Samplum.HostObjects;

namespace Samplum.Patches
{
	public class Sampler : Patch
	{
		private readonly object _bufferLock = new object();

		private float[] _sampleBuffer1;
		private float[] _sampleBuffer2;
		private long _length;
		private double _position;
		private bool _fileRead;

		public double O1;
		public double O2;

		private bool _fileChanged = true;
		private int _channels;
		private bool _isRunning;

		public double Speed = 1d;

		private string _fileName;
		public string FileName
		{
			get { return _fileName; }
			set
			{
				if (value == _fileName)
					return;
				_fileName = value;
				_fileChanged = true;
				Start();
			}
		}

		public void Start()
		{
			if (_fileChanged || !_fileRead)
				ChangeSample();
			_fileChanged = false;
			_isRunning = true;
			_position = 0;
		}

		public void Stop()
		{
			_isRunning = false;
			_position = 0;
		}

		public override void Ar()
		{
			if (!_isRunning)
				return;
			lock (_bufferLock)
			{
				if (!_fileRead)
					return;
				if (_length < 2)
					return;
				var position = (int)_position;
				if (position <= 0)
				{
					_position = _sampleBuffer1.Length - 1;
					position = (int)_position;
				}
				if (position >= _length)
				{
					_position = 0;
					position = 0;
				}

				O1 = _sampleBuffer1[position];
				O2 = _channels == 2 ? _sampleBuffer2[position] : O1;

				_position += Speed;
			}
		}

		private void ChangeSample()
		{
			ThreadPool.QueueUserWorkItem(o =>
			{
				try
				{
					if (!File.Exists(FileName))
						return;
					float[] sampleBuffer;

					var waveStream = Path.GetExtension(FileName) == ".wav"
						? (WaveStream)new WaveFileReader(FileName)
						: new Mp3FileReader(FileName);
					if (waveStream.WaveFormat.BitsPerSample != 16)
						throw new Exception("16bit required");
					if (waveStream.WaveFormat.Channels > 2)
						throw new Exception("1 or 2 channels required");
					var channels = waveStream.WaveFormat.Channels;

					using (var pcmStream = WaveFormatConversionStream.CreatePcmStream(waveStream))
					{
						var transformator = new Pcm16BitToSampleProvider(pcmStream);

						_length = pcmStream.Length / channels;
						sampleBuffer = new float[_length];
						transformator.Read(sampleBuffer, 0, sampleBuffer.Length);
					}

					lock (_bufferLock)
					{
						_fileRead = true;
						_channels = channels;

						if (channels == 1)
						{
							_sampleBuffer1 = sampleBuffer;
							_sampleBuffer2 = sampleBuffer;
						}
						else
						{
							// Must be 2
							_sampleBuffer1 = new float[sampleBuffer.Length / 2];
							_sampleBuffer2 = new float[sampleBuffer.Length / 2];
							for (var i = 0; i < sampleBuffer.Length / 2; i++)
							{
								_sampleBuffer1[i] = sampleBuffer[i * 2];
								_sampleBuffer2[i] = sampleBuffer[i * 2 + 1];
							}
						}
					}

					Log("Sample loaded: " + _fileName);
				}
				catch (Exception e)
				{
					Log(e.ToString());
				}
			});
		}
	}
}
